package com.atguigu.guli.ai.service;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.atguigu.common.core.domain.model.LoginUser;
import com.atguigu.common.utils.SecurityUtils;
import com.atguigu.guli.ai.constant.SystemConstant;
import com.atguigu.guli.ai.pojo.Chat;
import com.atguigu.guli.ai.pojo.Message;
import com.atguigu.guli.ai.strategy.AiBean;
import com.atguigu.guli.ai.strategy.AiOperator;
import com.atguigu.guli.ai.util.FileUtil;
import com.atguigu.guli.ai.util.MongoUtil;
import com.atguigu.guli.ai.vo.ChatVo;
import com.atguigu.guli.ai.vo.MessageVo;
import com.atguigu.guli.ai.vo.QueryVo;
import com.atguigu.system.domain.ChatKnowledge;
import com.atguigu.system.domain.ChatProject;
import com.atguigu.system.mapper.ChatKnowledgeMapper;
import com.atguigu.system.mapper.ChatProjectMapper;
import com.atguigu.system.service.IChatKnowledgeService;
import com.atguigu.system.service.impl.ChatKnowledgeServiceImpl;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.ChatOptionsBuilder;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.expression.Expression;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Ai操作的service，可以编写接口及实现类
 * 我这里直接写实现类
 */
@Service
public class AiService implements ApplicationContextAware {

    @Autowired
    private ChatKnowledgeMapper chatKnowledgeMapper;

    @Autowired
    private ChatProjectMapper chatProjectMapper;

    @Autowired
    private MongoTemplate mongoTemplate;

    private static final Map<String, AiOperator> MAP = new ConcurrentHashMap<>();

    /**
     * 统一获取spring容器中的AiOperator具体策略类，并放入map中方便进行切换
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(AiBean.class);
        if (CollectionUtils.isEmpty(beanMap)){
            return;
        }
        Collection<Object> beans = beanMap.values();
        beans.forEach(bean -> {
            AiBean aiBean = bean.getClass().getAnnotation(AiBean.class);
            MAP.put(aiBean.value(), (AiOperator) bean);
        });
    }

    public AiOperator getAiOperator(String type){
        return MAP.get(type);
    }

    @Transactional
    public void upload(ChatKnowledge chatKnowledge, MultipartFile file) {
        // 从文件中把内容取出来：通过工具类，工具类都是会用，不要求掌握它的实现
        String content = FileUtil.getContentFromFile(file);

        // 保存知识库到本地数据库（mysql）
        chatKnowledge.setFileName(file.getOriginalFilename());
        chatKnowledge.setContent(content);
        // 获取登录用户信息。
        LoginUser loginUser = SecurityUtils.getLoginUser();
        chatKnowledge.setUserId(loginUser.getUserId());
        chatKnowledge.setCreateBy(loginUser.getUsername());
        chatKnowledge.setCreateTime(new Date());
        this.chatKnowledgeMapper.insertChatKnowledge(chatKnowledge);

        // 保存知识库到向量数据库：projectId knowledgId content内容
        // 根据projectId查询项目（模型的类型）
        ChatProject chatProject = this.chatProjectMapper.selectChatProjectByProjectId(chatKnowledge.getProjectId());

        // 向向量数据库初始化知识库
        this.getAiOperator(chatProject.getType()).addDocs(chatKnowledge);
    }

    /**
     * 删除知识库
     */
    public void removeKnowledge() {

    }

    /**
     * 创建会话：使用的数据库是MongoDB
     *
     * @param chatVo
     * @return
     */
    public String createChat(ChatVo chatVo) {
        Chat chat = new Chat();
        BeanUtils.copyProperties(chatVo, chat);
        // 使用雪花算法生成唯一标识
        Snowflake snowflake = IdUtil.getSnowflake();
        Long chatId = snowflake.nextId();
        chat.setChatId(chatId);
        // 创建时间
        chat.setCreateTime(new Date());
        // 保存到MongoDB，并指定保存到MongoDB中的那个集合
        this.mongoTemplate.insert(chat, MongoUtil.getChatCollectionName(chatVo.getProjectId()));
        return chatId.toString();
    }

    public List<Chat> listChat(Long projectId, Long userId) {
        // 根据projectId和userId进行查询，并且根据创建时间降序排列
        return this.mongoTemplate.find(Query.query(Criteria
                        .where("projectId").is(projectId)
                        .and("userId").is(userId)).with(Sort.by(Sort.Order.desc("createTime"))),
                Chat.class, MongoUtil.getChatCollectionName(projectId));
    }

    public void updateChat(ChatVo chatVo) {
        // 根据id更新会话标题
        this.mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(chatVo.getChatId())),
                Update.update("title", chatVo.getTitle()), MongoUtil.getChatCollectionName(chatVo.getProjectId()));
    }

    public Flux<String> chatStream(QueryVo queryVo) {

        // 1.记录用户的问题：MongoDB的对应的聊天的集合中
        String collectionName = MongoUtil.getMsgCollectionName(queryVo.getChatId());
        Message message = new Message();
        // 聊天的id
        long msgId = IdUtil.getSnowflake().nextId();
        message.setId(msgId);
        message.setChatId(queryVo.getChatId());
        message.setType(0);
        message.setContent(queryVo.getMsg());
        message.setCreateTime(new Date());
        this.mongoTemplate.insert(message, collectionName);

        // 2.查询本地知识库：Qdrant
        // 根据项目id查询项目
        ChatProject chatProject = this.chatProjectMapper.selectChatProjectByProjectId(queryVo.getProjectId());
        if (chatProject == null) {
            throw new RuntimeException("对应的项目不存在！");
        }
        // 获取项目所采用的模型类型
        String type = chatProject.getType();
        List<Document> docs = this.getAiOperator(type).similaritySearch(queryVo); // 本地知识库中的内容，要作为系统提示

        // 3.查询历史问答，作为联系上下文的提示
        List<Message> messages = this.mongoTemplate.find(Query
                .query(Criteria.where("chatId").is(queryVo.getChatId()))
                .with(Sort.by(Sort.Order.asc("createTime"))), Message.class, collectionName);

        // 组装上下文提示
        List<org.springframework.ai.chat.messages.Message> msgs = new ArrayList<>();
        if (!CollectionUtils.isEmpty(messages)) { // 用户问答提示
            messages.forEach(message1 -> {
                org.springframework.ai.chat.messages.Message msg = null;
                if (message1.getType().intValue() == 0) { // 如果type为0，则说明是用户的提问UserMessage
                    msg = new UserMessage(message1.getContent());
                } else { // 如果type为0，则说明是AI的回答内容
                    msg = new AssistantMessage(message1.getContent());
                }
                msgs.add(msg);
            });
        }
        // 系统提示：SystemMessage
        if (!CollectionUtils.isEmpty(docs)) {
            docs.forEach(doc -> {
                msgs.add(new SystemMessage(doc.getContent()));
            });
        }

        // 4.发送请求给大模型，获取问答结果
        Flux<String> result = this.getAiOperator(type).chat_stream(msgs);

        // 5.ai模型的响应结果，如果我直接从流中提取结果的话和页面从流中提取的结果 完全不一样
        return result;
    }

    public void saveMsg(MessageVo messageVo) {
        Message message = new Message();
        // 生成聊天消息的id
        message.setId(IdUtil.getSnowflake().nextId());
        message.setChatId(messageVo.getChatId());
        message.setType(1);
        message.setContent(messageVo.getContent());
        message.setCreateTime(new Date());
        this.mongoTemplate.insert(message, MongoUtil.getMsgCollectionName(messageVo.getChatId()));
    }

    public List<Message> listMsg(Long chatId) {

        return this.mongoTemplate.find(Query
                        .query(Criteria.where("chatId").is(chatId))
                        .with(Sort.by(Sort.Order.asc("createTime"))),
                Message.class, MongoUtil.getMsgCollectionName(chatId));
    }


}
